/*
 *	Copyright (c) 2009, libBasement
 *	All rights reserved.
 *	
 *	Redistribution and use in source and binary forms, with or without modification,
 *	are permitted provided that the following conditions are met:
 *	
 *	Redistributions of source code must retain the above copyright notice,
 *	this list of conditions and the following disclaimer.
 *	Redistributions in binary form must reproduce the above copyright notice,
 *	this list of conditions and the following disclaimer in the documentation
 *	and/or other materials provided with the distribution.
 *	Neither the name of the <ORGANIZATION> nor the names of its contributors may
 *	be used to endorse or promote products derived from this software without
 *	specific prior written permission.
 *	THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 *	AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 *	THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 *	ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
 *	FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 *	(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 *	LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 *	ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 *	(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 *	SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "BDictionary.h"
#include "BString.h"
#include "BException.h"

namespace Basement
{
	void Dictionary::dealloc()
	{
		_keys->release();
		_keys = NULL;
		
		_values->release();
		_values = NULL;
		
		Object::dealloc();
	}
	
#pragma mark -
	
	Dictionary *Dictionary::withObjectsAndKeys(Array *objects, Array *keys)
	{
		return Dictionary::alloc()->initWithObjectsAndKeys(objects, keys)->autorelease< Dictionary * >();
	}
	
	Dictionary *Dictionary::withDictionary(Dictionary *dictionary)
	{
		return Dictionary::alloc()->initWithDictionary(dictionary)->autorelease< Dictionary * >();
	}
	
#pragma mark -
	
	Dictionary *Dictionary::init()
	{
		if(Object::init())
		{
			_keys = Array::create< Array >();
			_values = Array::create< Array >();
			
			return this;
		}
		return NULL;
	}
	
	Dictionary *Dictionary::initWithObjectsAndKeys(Array *objects, Array *keys)
	{
		if(this->init())
		{
			_keys->addObjectsFromArray(keys);
			_values->addObjectsFromArray(objects);
			return this;
		}
		return NULL;
	}
	
	Dictionary *Dictionary::initWithDictionary(Dictionary *dictionary)
	{
		return this->initWithObjectsAndKeys(dictionary->allValues(), dictionary->allKeys());
	}
	
#pragma mark Basic
	
	UInteger Dictionary::count() const
	{
		return _keys->count();
	}
	
	Object *Dictionary::objectForKey(Object *key)
	{
		BParamaterAssert(key);
		
		UInteger index = _keys->indexOfObject(key);
		if(index != Range::NotFound)
		{
			return _values->objectAtIndex(index);
		}
		return NULL;
	}
	
	Array *Dictionary::objectsForKeys(Array *keys, Object *notFoundMarker)
	{
		BParamaterAssert(keys && notFoundMarker);
		
		Array *objects = Array::array();
		
		forall(Object *, key, keys)
		{
			Object *value = this->objectForKey(key);
			if(value)
			{
				objects->addObject(value);
			}
			else
			{
				objects->addObject(notFoundMarker);
			}
		}
		
		return objects;
	}
	
	String *Dictionary::description()
	{
		String *description = $("{\n");
		
		Array::ObjectEnumerator objectEnumerator = _keys->objectEnumerator();
		Object *key;
		while (key = objectEnumerator.nextObject())
		{
			description->appendString($("\t"));
			description->appendString(key->description());
			description->appendString($(" = "));
			description->appendString(this->objectForKey(key)->description());
			description->appendString($("\n"));
		}
		
		description->appendString($("}"));
		
		return description;
	}
	
#pragma mark Enumeration
	
	Array *Dictionary::allKeysForObject(Object *object)
	{
		BParamaterAssert(object);
		
		Array *array = Array::array();
		
		for (Integer index = 0; index < _values->count(); index++)
		{
			Object *value = _values->objectAtIndex(index);
			if(value->isEqualTo(object))
			{
				array->addObject(_keys->objectAtIndex(index));
			}
		}
		
		return array;
	}
	
#pragma mark Equality
	
	bool Dictionary::isEqualToDictionary(Dictionary *dictionary) const
	{
		if(!dictionary) return false;
		
		return _keys->isEqualToArray(dictionary->allKeys()) && _values->isEqualToArray(dictionary->allValues());
	}
	
#pragma mark Mutation
	
	Dictionary *Dictionary::removeObjectForKey(Object *key)
	{
		BParamaterAssert(key);
		
		UInteger index = _keys->indexOfObject(key);
		if(index != Range::NotFound)
		{
			_values->removeObjectAtIndex(index);
			_keys->removeObjectAtIndex(index);
		}
		return this;
	}
	
	Dictionary *Dictionary::setObjectForKey(Object *object, Object *key)
	{
		BParamaterAssert(object && key);
		
		this->willChangeValueForKey(key->description());
		
		UInteger index = _keys->indexOfObject(key);
		if(index != Range::NotFound)
		{
			_values->removeObjectAtIndex(index);
			_values->insertObject(object, index);
		}
		else
		{
			_keys->addObject(key);
			_values->addObject(object);
		}
		
		this->didChangeValueForKey(key->description());
		
		return this;
	}
	
	Dictionary *Dictionary::removeAllObjects()
	{
		_keys->removeAllObjects();
		_values->removeAllObjects();
		return this;
	}
	
#pragma mark -
	
	Dictionary *Dictionary::addEntriesFromDictionary(Dictionary *other)
	{
		forall(Object *, key, other->allKeys())
		{
			this->setObjectForKey(other->objectForKey(key), key);
		}
		
		return this;
	}
	
	Dictionary *Dictionary::removeObjectsForKeys(Array *keys)
	{
		forall(Object *, key, keys)
		{
			this->removeObjectForKey(key);
		}
		
		return this;
	}
	
	Dictionary *Dictionary::setDictionary(Dictionary *other)
	{
		this->removeAllObjects();
		this->addEntriesFromDictionary(other);
		
		return this;
	}
	
#pragma mark Theoretical KVC
	
	void Dictionary::setValueForKey(Object *value, String *key)
	{
		this->setObjectForKey(value, key);
	}
	
	Object *Dictionary::valueForKey(Object *value, String *key)
	{
		return this->objectForKey(key);
	}
}
